#ifndef _DAQ_H
 #define _DAQ_H

#include <comedilib.h>
#include <iostream>

/// defaults
#define D_FILENAME "/dev/comedi0"
#define D_NCHANNELS 1
#define D_FREQUENCY 1000.0
#define D_NSAMPLES 1000

#define D_SUBDEVICE 0
#define D_DATABUFFER 1000
// this can probably just be 16
//#define MAX_CHANNELS 256
#define MAX_CHANNELS 16
// TODO this stuff needs looked into
// what does range do?
// what about aref?
// what are reasonable values?
#define BASE_CHANNEL 0
#define D_RANGE 0
#define D_AREF AREF_GROUND

// print physical values
//#define PHYSICAL

/**
 * \file daq.h
 * Data acquisition library sitting on top of comedilib.h
 * \author Brett Graham
 * \date 4-16-08
 * \todo TODO
 * - add 'verbosity'
 * - add calibration
*/

/*

// possible uses:
		in experiment:
			record for x-seconds/samples
		out-of-experiment:
			record continuously and display
	basic setup:
		1. configure
		2. test commands
		3. check everything
		4. start sampling
			if stop reached -> stopped
			if continuous -> keep going

	Daq class:
		variables:
			int nChannels
			double frequency
			int subdevice
			bool verbose
			char *filename
			bool continuous
			int nSamples
		structures:
			comedi_cmd cmd;
			comedi_insn insn;
			comedi_t *dev;
		functions:
			start_recording
			stop_recording
			set_nSamples
			set_nMilliseconds		
*/

char *command_test_messages[]={
	"success",
	"invalid source",
	"source conflict",
	"invalid argument",
	"argument conflict",
	"invalid chanlist",
};

char *command_src(int src,char *buf)
{
	buf[0]=0;

	if(src&TRIG_NONE)strcat(buf,"none|");
	if(src&TRIG_NOW)strcat(buf,"now|");
	if(src&TRIG_FOLLOW)strcat(buf, "follow|");
	if(src&TRIG_TIME)strcat(buf, "time|");
	if(src&TRIG_TIMER)strcat(buf, "timer|");
	if(src&TRIG_COUNT)strcat(buf, "count|");
	if(src&TRIG_EXT)strcat(buf, "ext|");
	if(src&TRIG_INT)strcat(buf, "int|");
#ifdef TRIG_OTHER
	if(src&TRIG_OTHER)strcat(buf, "other|");
#endif

	if(strlen(buf)==0){
		sprintf(buf,"unknown(0x%08x)",src);
	}else{
		buf[strlen(buf)-1]=0;
	}

	return buf;
}

void dump_command(comedi_cmd *command)
{
	char buf[100];

	std::clog << "\tstart: " << command_src(command->start_src,buf) << " " << command->start_arg << "\n";
//	fprintf(out,"start:      %-8s %d\n",
//		cmd_src(cmd->start_src,buf),
//		cmd->start_arg);

	std::clog << "\tscan_begin: " << command_src(command->scan_begin_src,buf) << " " << command->scan_begin_arg << "\n";
//	fprintf(out,"scan_begin: %-8s %d\n",
//		cmd_src(cmd->scan_begin_src,buf),
//		cmd->scan_begin_arg);

	std::clog << "\tconvert: " << command_src(command->convert_src,buf) << " " << command->convert_arg << "\n";
//	fprintf(out,"convert:    %-8s %d\n",
//		cmd_src(cmd->convert_src,buf),
//		cmd->convert_arg);

	std::clog << "\tscan_end: " << command_src(command->scan_end_src,buf) << " " << command->scan_end_arg << "\n";
//	fprintf(out,"scan_end:   %-8s %d\n",
//		cmd_src(cmd->scan_end_src,buf),
//		cmd->scan_end_arg);

	std::clog << "\tstop: " << command_src(command->stop_src,buf) << " " << command->stop_arg << "\n";
//	fprintf(out,"stop:       %-8s %d\n",
//		cmd_src(cmd->stop_src,buf),
//		cmd->stop_arg);
}


/// Daq
/**
	Data acquisition device class
	A child to this class can be created to allow custom handling of data. For example: 
	\code
	class NewDaq : public Daq {
		public:
			NewDaq(char* filename = D_FILENAME);
		protected:
			void process_physical_datum(double physical_value, int channel);
	};

	NewDaq::NewDaq(char* filename) : Daq(filename) {
	}

	void NewDaq::process_physical_datum(double physical_value, int channel) {
		std::cout << "new processor : " << physical_value << " ";
		if (channel == (nChannels -1)) {
			std::cout << "\n";
		}
	};
	\endcode
*/
class Daq {
	public:
		Daq(char* filename = D_FILENAME);
		~Daq();
		int test_command();
		int start_recording();
		int stop_recording();
		/// accessor functions
		int set_nSamples(int nSamples);
		int get_nSamples();
		int set_nMilliseconds(int nMilliseconds);
		int set_continuous(bool continuous);
		int set_nChannels(int nChannels);
		int get_nChannels();
		/// TODO does frequency need to be a double?
		int set_frequency(double frequency);
		double get_frequency();
		//int set_subdevice(int subdevice);
		void set_verbose(bool verbose);
		void set_physical(bool physical);
		// this function must be public so that it can be overrided
		//void process_physical_datum(double physical_value, int channel);
	protected:
		int nChannels;
		double frequency;
		//int subdevice; TODO at the moment I am not using this, nor do I think this will ever change
		bool verbose;
		bool physical;
		//char *filename;
		bool continuousSampling;
		int nSamples;
		bool ready;
		// structures for comedi control of the daq device
		comedi_cmd command;
		comedi_insn instruction;
		lsampl_t instructionData[1]; //TODO what is this?
		comedi_t *device;
		char dataBuffer[D_DATABUFFER];
		unsigned int chanlist[MAX_CHANNELS];
		comedi_range * range_info[MAX_CHANNELS];
		lsampl_t maxdata[MAX_CHANNELS];
		// private functions
		void setup_command();
		void setup_instruction();
		virtual void process_physical_datum(double physical_value, int channel);
		virtual void process_raw_datum(double raw_value, int channel);
		
};

/// Daq Constructor
/**
	Create space for all comedi structures and assign defaults to daq variables. Sets ready flag to false
*/
Daq::Daq(char* filename) {
	// ready = false, we're not ready to sample
	ready = false;

	// -- allocate space for and open device
	std::clog << "Daq:: Attempting comedi_open on " << filename;
	device = comedi_open(filename);
	if (device == NULL) {
		std::clog << " Error, open failed\n";
		std::cerr << "Daq:: comedi_open(" << filename << ") failed\n";
		//fprintf(stderr, "error opening %s\n", options.filename);
		//return -1;
		//TODO raise an exception or something here, program should stop gracefully
	} else {
		std::clog << " success\n";
	};

	// -- allocate space for command --
	memset(&command,0,sizeof(command)); /* what does this do? */

	// -- allocate space for instruction --
	memset(&instruction,0,sizeof(instruction));
	setup_instruction();

	// == assign defaults ==
	// watch these TODO each of these should raise an exception if all did not go well (return > 0)
	frequency = D_FREQUENCY;
	nSamples = D_NSAMPLES;
	verbose = false;
	physical = true;
	continuousSampling = false;
	// set_nchannels() also calls setup_command()
	set_nChannels(D_NCHANNELS);
	
//	set_nChannels(D_NCHANNELS);
//	set_frequency(D_FREQUENCY);
//	set_nSamples(D_NSAMPLES);
//	set_continuous(false);
//	set_verbose(false);
}

Daq::~Daq(){
	ready = false;
	// check if device is open
	if (device != NULL) {
		if (comedi_close(device) != 0) {
			std::cerr << "Daq:: in ~Daq(): error closing comedi device\n";
		};
	};
}

void Daq::setup_command() {
	ready = false;
	command.subdev = D_SUBDEVICE;
	command.flags = 0;
	command.start_src = TRIG_INT; /* start event occurs on a Comedi internal signal which is typically caused by an INSN_TRIG instruction */
	command.start_arg = 0;
	command.scan_begin_src = TRIG_TIMER; /* timing of scan_begin events are seperated by x nanoseconds (set in scan_begin_arg) */
	// TODO, this should be set based on frequency
	// 1000 samples per second -> nanoseconds per sample
	//command.scan_begin_arg = 50000; /* nanoseconds between scan_begin events */
	command.scan_begin_arg = (unsigned int)(1000000000.0/frequency);
	command.convert_src = TRIG_TIMER; /* time between scans is x nanoseconds (set in convert_arg) */
	command.convert_arg = 0; /* even though this is invalid, comedi_command_test() will fix it later */
	command.scan_end_src = TRIG_COUNT; /* stop scanning when x channels (set in scan_end_arg) have been scanned */
	command.scan_end_arg = nChannels;
	if (continuousSampling) {
		command.stop_src = TRIG_NONE;  /* never stop (until comedi_cancel() ) */
		command.stop_arg = 0;
	} else {
		command.stop_src = TRIG_COUNT;
		command.stop_arg = nSamples;
	}
	command.chanlist = chanlist;
	command.chanlist_len = nChannels;
}

void Daq::setup_instruction() {
	instruction.insn = INSN_INTTRIG;
	instruction.subdev = D_SUBDEVICE;
	instruction.data = instructionData;
	instruction.n = 1;
	instructionData[0] = 0; //TODO what is this?
}

int Daq::set_nChannels(int nChannels) {
	ready = false;
	if ((nChannels < 0) or (nChannels > MAX_CHANNELS)) {
		std::cerr << "Daq:: set_nChannels("<< nChannels <<") failed due to invalid nChannels value\n";
		return 1;
	}
	this->nChannels = nChannels;
	// TODO setup channel list
	/* set up channel list */
	for(int i = 0; i < nChannels; i++){
		chanlist[i] = CR_PACK(BASE_CHANNEL + i, D_RANGE, D_AREF);
		range_info[i] = comedi_get_range(device, D_SUBDEVICE, BASE_CHANNEL, D_RANGE);
		maxdata[i] = comedi_get_maxdata(device, D_SUBDEVICE, BASE_CHANNEL);
	}
	// TODO update command
	setup_command();
	return 0;
}

int Daq::get_nChannels() {
	return nChannels;
}

int Daq::set_frequency(double frequency) {
	ready = false;
	if (frequency < 0) {
		std::cerr << "Daq:: set_frequency("<< frequency << ") failed due to invalid frequency value\n";
		return 1;
	}
	this->frequency = frequency;
	setup_command();
	return 0;
}

double Daq::get_frequency() {
	return frequency;
}

int Daq::set_nSamples(int nSamples) {
	ready = false;
	if (nSamples < 0) {
		std::cerr << "Daq:: set_nSamples("<< nSamples <<") failed due to invalid nSamples value\n";
		return 1;
	}
	this->nSamples = nSamples;
	setup_command();
	return 0;
}

int Daq::get_nSamples() {
	return nSamples;
}

int Daq::set_continuous(bool continuous) {
	this->continuousSampling = continuous;
	setup_command();
	return 0;
}

void Daq::set_verbose(bool verbose) {
	this->verbose = verbose;
}

void Daq::set_physical(bool physical) {
	this->physical = physical;
}

int Daq::test_command() {
	/* test command */
	std::clog << "Daq:: test_command:\n";
	//fprintf(stderr, "command before testing:\n");
	std::clog << "\t--Before testing:\n";
	dump_command(&command);
	int ret = comedi_command_test(device, &command);
	if(ret < 0){
		comedi_perror("comedi_command_test");
//		TODO where is errno defined?
//		if(errno == EIO){
//			std::cerr << "Daq:: (in test_command()): this subdevice(" << D_SUBDEVICE << ") does not support commands\n";
//			//fprintf(stderr,"Ummm... this subdevice doesn't support commands\n");
//		}
		// return a >0 number, meaning the test failed
		return 1;
		//exit(1);
	}
	std::clog << "\t\t**Test returned: "<< ret << " " << command_test_messages[ret] << "\n";
//	fprintf(stderr,"first test returned %d (%s)\n", ret,
//			cmdtest_messages[ret]);
	std::clog << "\t--Command after test:\n";
	dump_command(&command);
	if (ret == 0) { // success!!!
		// now update the class structure so that the class variables (freq, etc...) contain the arguments that 'passed' the test
		// frequency ~~~~ command.scan_begin_arg = (unsigned in)(1000000000.0/frequency);
		frequency = 1000000000.0/command.scan_begin_arg;
		// nChannels ~~~~ command.scan_end_arg = nChannels;
		nChannels = command.scan_end_arg;
		// nSamples ~~~~ command.stop_arg = nSamples;
		nSamples = command.stop_arg;
		ready = true;
	} else {
		ready = false;
	}
	return ret;
//	ret = comedi_command_test(dev, &cmd);
//	if(ret < 0){
//		comedi_perror("comedi_command_test");
//		exit(1);
//	}
//	fprintf(stderr,"second test returned %d (%s)\n", ret,
//			cmdtest_messages[ret]);
//	if(ret!=0){
//		dump_cmd(stderr, &cmd);
//		fprintf(stderr, "Error preparing command\n");
//		exit(1);
//	}
}

/// Start Sampling
/**
	Passes the command and instruction to the comedi device and begins sampling. This call blocks until either nSamples are reached or comedi_cancel is called via stop_recording()
*/
int Daq::start_recording() {
	// if everything is not ready, than just return 1
	if (ready = false) {
		std::cerr << "Daq:: start_recording() called when ready = false. Did you test the command?\n";
		return 1;
	}
	// TODO print out options: nSamples, continuous...
	// send command to device
	if (verbose) std::clog << "Daq:: in start_recording(): sending command to device\n";
	int ret = comedi_command(device, &command);
	if(ret < 0){
		//comedi_perror("comedi_command");
		std::cerr << "Daq:: start_recording(): comedi_command(device, &command) failed\n";
		return 1;
	}
	// send instruction to begin recording
	if (verbose) std::clog << "Daq:: in start_recording(): sending instruction to device\n";
	comedi_do_insn(device, &instruction);
	
	// process/output data
	int subdevice_flags = comedi_get_subdevice_flags(device, D_SUBDEVICE);
	int total = 0;
	float raw;
	while(1){
		//std::cerr << "attemping read\n";
		ret = read(comedi_fileno(device),dataBuffer,D_DATABUFFER);
		//std::cerr << "read returned "<< ret <<" bytes\n";
		if(ret < 0){
			/* some error occurred */
			std::cerr << "Daq:: in start_recording(): read error\n";
			//perror("read");
			break;
		}else if(ret == 0){
			/* reached stop condition */
			//std::cerr << comedi_poll(device, D_SUBDEVICE) << " bytes left to read\n";
			if (verbose) {
				int bytes_per_sample;
				if(subdevice_flags & SDF_LSAMPL)
					bytes_per_sample = sizeof(lsampl_t);
				else
					bytes_per_sample = sizeof(sampl_t);
				std::clog << "Daq:: done sampling: read " << total << " bytes, " << total / bytes_per_sample << " samples\n";
			}
			break;
		}else{
			static int col = 0;
			int bytes_per_sample;
			total += ret;
			//if(options.verbose)fprintf(stderr, "read %d %d\n", ret, total);
			//TODO pull this outside the loop
			if(subdevice_flags & SDF_LSAMPL)
				bytes_per_sample = sizeof(lsampl_t);
			else
				bytes_per_sample = sizeof(sampl_t);
			for(int i = 0; i < ret / bytes_per_sample; i++){
				if(subdevice_flags & SDF_LSAMPL) {
					raw = ((lsampl_t *)dataBuffer)[i];
				} else {
					raw = ((sampl_t *)dataBuffer)[i];
				}
				/* print_datum(raw, col, options.physical); */
				//#ifdef PHYSICAL
				if(physical) {
					double physical_value;
					// the following line (now commented out), was throwing a warning durin compilation
					// physical_value = comedi_to_phys(raw, range_info[col], maxdata[col]);
					physical_value = comedi_to_phys((lsampl_t)raw, range_info[col], maxdata[col]);
					//physical_value = comedi_to_phys(raw, range_info[channel_index], maxdata[channel_index]);
					process_physical_datum(physical_value, col);
					//std::cout << physical_value << " ";
				} else {
				//#else
					process_raw_datum(raw, col);
					//std::cout << raw << " ";
				}
				//#endif
//					if(!options.physical) {
//						std::cout << raw << "\n";
//						//printf("%f ",raw);
//					} else {
//						physical_value = comedi_to_phys(raw, range_info[col], maxdata[col]);
//						//physical_value = comedi_to_phys(raw, range_info[channel_index], maxdata[channel_index]);
//						std::cout << physical_value << "\n";
//						//printf("%#8.6g ",physical_value);
//					}
				col++;
				if(col == nChannels){
					//printf("\n");
					col=0;
				}
			}
		}
	}
	return 0;
}

/// Stop sampling
/**
	calls comedi_cancel()
*/
int Daq::stop_recording() {
	if (comedi_cancel(device, D_SUBDEVICE) == 0) {
		// this does not exit correctly if the buffer is still being read
		return 0;
	} else {
		return 1;
	}
}

void Daq::process_physical_datum(double physical_value, int channel) {
	std::cout << physical_value << " ";
	if (channel == (nChannels-1)) {
		std::cout << "\n";
	}
}

void Daq::process_raw_datum(double raw_value, int channel) {
	std::cout << raw_value << " ";
	if (channel == (nChannels-1)) {
		std::cout << "\n";
	}
}

#endif
